1 module d_snprintf.vararg;
2 
3 @nogc:
4 nothrow:
5 
6 version (D_TypeInfo) {
7     alias Type = TypeInfo;
8 } else {
9     // Without TypeInfo we use an opaque hash to identify types.
10     alias Type = size_t;
11 }
12 
13 struct va_list {
14     void*[] values;
15     Type[] types;
16     size_t current;
17 
18     @nogc:
19     nothrow:
20 
21     int opApply(int delegate(va_elem) nothrow @nogc dg) {
22         int result = 0;
23 
24         for (size_t i = current; i < values.length; i++) {
25             result = dg(va_elem(values[i], types[i]));
26             if (result) {
27                 break;
28             }
29         }
30         return result;
31     }
32 }
33 
34 struct va_elem {
35     void* value;
36     Type type;
37 }
38 
39 mixin template va_start(a...) {
40     ubyte[a.length * (size_t.sizeof) * 2] va_args_buffer;
41     va_list va_args = get_varargs(a, va_args_buffer[]);
42 }
43 pragma(inline, true) void va_end(va_list) {}
44 
45 pragma(inline, true) void va_copy(ref va_list copy, ref va_list args) {
46     copy = args;
47 }
48 
49 va_list get_varargs(A...)(ref A a, ubyte[] buffer) {
50     va_list result;
51     result.values = (cast(void**)buffer.ptr)[0..a.length];
52     result.types = (cast(Type*)buffer.ptr)[a.length..a.length * 2];
53     static foreach (i, t; a) {
54         result.values[i] = cast(void*)&t;
55         result.types[i] = va_get_type!(typeof(t));
56     }
57     return result;
58 }
59 
60 static if (size_t.sizeof == 8) {
61     private enum TYPE_MASK      = 0x0FFFFFFFFFFFFFFF;
62     private enum TYPE_POINTER   = 0x1000000000000000;
63     private enum TYPE_ARRAY     = 0x2000000000000000;
64     private enum TYPE_ENUM      = 0x4000000000000000;
65     private enum TYPE_CLASS     = 0x8000000000000000;
66 } else {
67     private enum TYPE_MASK      = 0x0FFFFFFF;
68     private enum TYPE_POINTER   = 0x10000000;
69     private enum TYPE_ARRAY     = 0x20000000;
70     private enum TYPE_ENUM      = 0x40000000;
71     private enum TYPE_CLASS     = 0x80000000;
72 }
73 
74 Type va_get_type(T)() {
75     version(D_TypeInfo) {
76         return strip_type_info(typeid(T));
77     } else {
78         import std.traits : Unqual, isPointer, OriginalType;
79         size_t result = 0;
80         alias stripped_T = Unqual!T;
81         static if (isPointer!(stripped_T)) {
82             alias actual_T = PointerTarget!stripped_T;
83             result |= TYPE_POINTER;
84         } else static if (is(stripped_T : E[], E)) {
85             alias actual_T = E;
86             result |= TYPE_ARRAY;
87         } else static if (is(stripped_T == enum)) {
88             alias actual_T = OriginalType!stripped_T;
89             result |= TYPE_ENUM;
90         } else static if (is(stripped_T == class)) {
91             alias actual_T = stripped_T;
92             result |= TYPE_CLASS;
93         } else {
94             alias actual_T = stripped_T;
95         }
96 
97         // We just have to hope this is unique.
98         result |= get_type_hash!(Unqual!actual_T) & TYPE_MASK;
99         return result;
100     }
101 }
102 
103 private template get_type_hash(T) {
104     // Removes const, immutabe, shared and inout
105     enum get_type_hash = fnv_1a_hash(T.mangleof);
106 }
107 
108 private size_t fnv_1a_hash(string s) {
109     static if (size_t.sizeof == 8) {
110         enum fnv_prime = 0x100000001b3;
111         enum fnv_offset_basis = 0xcbf29ce484222000;
112     } else {
113         enum fnv_prime = 0x1000193;
114         enum fnv_offset_basis = 0x811c9dc5;
115     }
116     size_t hash = fnv_offset_basis;
117     foreach (char value; s) {
118             hash = hash ^ value;
119             hash = hash * fnv_prime;
120     }
121     return hash;
122 }
123 
124 pragma(inline, true) T va_arg(T)(ref va_list list) {
125     return *(cast(T*)list.values[list.current++]);
126 }
127 
128 pragma(inline, true) T va_peek(T)(ref va_list list) {
129     return *(cast(T*)list.values[list.current]);
130 }
131 
132 pragma(inline, true) va_elem va_get_elem(ref va_list list) {
133     return va_elem(list.values[list.current], list.types[list.current]);
134 }
135 
136 pragma(inline, true) va_elem va_get_elem(ref va_list list, int i) {
137     return va_elem(list.values[list.current + i], list.types[list.current + i]);
138 }
139 
140 pragma(inline, true) T va_value(T)(va_elem elem) {
141     return *(cast(T*)elem.value);
142 }
143 
144 pragma(inline, true) size_t va_size(va_list list) {
145     return list.types.length - list.current;
146 }
147 
148 pragma(inline, true) Type va_get_type(va_list list) {
149     return list.types[list.current];
150 }
151 
152 pragma(inline, true) bool va_is_enum(Type type) {
153     version(D_TypeInfo) {
154         return (cast(TypeInfo_Enum)type) !is null;
155     } else {
156         return (type & TYPE_ENUM) != 0;
157     }
158 }
159 
160 pragma(inline, true) bool va_is_pointer(Type type) {
161     version(D_TypeInfo) {
162         return (cast(TypeInfo_Pointer)type) !is null;
163     } else {
164         return (type & TYPE_POINTER) != 0;
165     }
166 }
167 
168 pragma(inline, true) bool va_is_class(Type type) {
169     version(D_TypeInfo) {
170         return (cast(TypeInfo_Class)type) !is null;
171     } else {
172         return (type & TYPE_CLASS) != 0;
173     }
174 }
175 
176 pragma(inline, true) bool va_is_array(Type type) {
177     version(D_TypeInfo) {
178         return (cast(TypeInfo_Array)type) !is null;
179     } else {
180         return (type & TYPE_ARRAY) != 0;
181     }
182 }
183 
184 pragma(inline, true) Type va_get_enum_base_type(Type type) {
185     if (va_is_enum(type)) {
186         version(D_TypeInfo) {
187             return strip_type_info((cast(TypeInfo_Enum)type).base);
188         } else {
189             return type & TYPE_MASK;
190         }
191     }
192     return type;
193 }
194 
195 pragma(inline, true) Type va_get_pointer_target_type(Type type) {
196     if (va_is_pointer(type)) {
197         version(D_TypeInfo) {
198             return strip_type_info((cast(TypeInfo_Pointer)type).m_next);
199         } else {
200             return type & TYPE_MASK;
201         }
202     }
203     return type;
204 }
205 
206 pragma(inline, true) Type va_get_array_elem_type(Type type) {
207     assert(va_is_array(type));
208     version(D_TypeInfo) {
209         return type = strip_type_info((cast(TypeInfo_Array)type).value);
210     } else {
211         return type & TYPE_MASK;
212     }
213 }
214 
215 version (D_TypeInfo) {
216     // Removes const, immutabe, shared and inout
217     TypeInfo strip_type_info(TypeInfo type) {
218 
219         while ((cast(TypeInfo_Const)type) !is null) {
220             type = (cast(TypeInfo_Const)type).base;
221         }
222         return type;
223     }
224 }
225 
226 void* get_any_pointer(ref va_list list) {
227     Type type = va_get_type(list);
228     if (va_is_pointer(type)) {
229         void* result = va_arg!(void*)(list);
230         return result;
231     } else if (va_is_array(type)) {
232         void[] result = va_arg!(void[])(list);
233         return result.ptr;
234     } else if (type is va_get_type!(size_t)) {
235         size_t result = va_arg!(size_t)(list);
236         return cast(void*)result;
237     } else if (va_is_class(type)) {
238         Object result = va_arg!(Object)(list);
239         return cast(void*)result;
240     } else {
241         assert(false, "Tried to read a pointer from varargs but found a different type.");
242     }
243 }
244 
245 long get_any_int(ref va_list list) {
246     Type type = va_get_type(list);
247     type = va_get_enum_base_type(type);
248     if (va_get_type!(byte) is type) {
249         return va_arg!byte(list);
250     } else if (va_get_type!(ubyte) is type) {
251         return va_arg!ubyte(list);
252     } else if (va_get_type!(short) is type) {
253         return va_arg!short(list);
254     } else if (va_get_type!(ushort) is type) {
255         return va_arg!ushort(list);
256     } else if (va_get_type!(int) is type) {
257         return va_arg!int(list);
258     } else if (va_get_type!(uint) is type) {
259         return va_arg!uint(list);
260     } else if (va_get_type!(long) is type) {
261         return va_arg!long(list);
262     } else if (va_get_type!(ulong) is type) {
263         return va_arg!ulong(list);
264     } else if (va_get_type!(bool) is type) {
265         return va_arg!bool(list) ? 1 : 0;
266     } else {
267         assert(false, "Tried to read a integer from varargs but found a different type.");
268     }
269 }
270 
271 real get_any_float(ref va_list list) {
272     Type type = va_get_type(list);
273     type = va_get_enum_base_type(type);
274     if (va_get_type!(float) is type) {
275         return va_arg!float(list);
276     } else if (va_get_type!(double) is type) {
277         return va_arg!double(list);
278     } else if (va_get_type!(real) is type) {
279         return va_arg!real(list);
280     } else {
281         assert(false, "Tried to read a floating point number from varargs but found a different type.");
282     }
283 }
284 
285 // Look for string, char[] or (u)byte[]
286 bool has_string_like_value(ref va_list list) {
287     Type type = va_get_type(list);
288     if (va_get_type!(string) is type) {
289         return true;
290     } else if (va_is_array(type)) {
291         type = va_get_array_elem_type(type);
292         if (va_get_type!(char) is type || va_get_type!(byte) is type || va_get_type!(ubyte) is type) {
293             return true;
294         }
295     }
296     return false;
297 }